using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Linq;

namespace GuavaData
{
    public interface Function
    {
        bool IsInvertible { get; }
        Function Inverse { get; }
        List<Domain.Domain> inputs { get; }
        List<Domain.Domain> outputs { get; }
        object[] apply(params object[] o);
        XElement ToXElement();
    }

    public interface SqlFunctionDefintion
    {
        string SqlString(int position, params string[] columnInputs);
    }

    public class FunctionException : Exception
    {
        public FunctionException(string s)
            : base(s)
        {
            return;
        }

        public FunctionException(string s, Exception e)
            : base(s, e)
        {
            return;
        }
    }

    public class CurrentTime : Object, Function
    {
        #region Function Members


        public bool IsInvertible
        {
            get { return false; }
        }

        public Function Inverse
        {
            get { return null; }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get { return new List<GuavaData.Domain.Domain>(); }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                ld.Add(new Domain.DateTime());
                return ld;
            }
        }

        public object[] apply(params object[] o)
        {
            if (o.Length > 0) throw new FunctionException(String.Concat("Function CurrentTime requires 0 inputs, not ", o.Length.ToString(), "."));
            return new object[] { DateTime.Now };
        }

        public XElement ToXElement()
        {
            return new XElement("Function", this.GetType().ToString());
        }

        #endregion
    }

    public class FunctionFromProperty : Object, Function
    {
        MethodInfo property;

        public FunctionFromProperty(string qualifiedclassname, string propname)
        {
            Type t = Type.GetType(qualifiedclassname);
            PropertyInfo p = t.GetProperty(propname);
            property = p.GetGetMethod();
            if (!(property.IsStatic)) throw new Exception("Cannot use a non-static method to generate a function object out of a property.");
        }

        #region Function Members

        public bool IsInvertible
        {
            get { return false; }
        }

        public Function Inverse
        {
            get { return null; }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get { return new List<GuavaData.Domain.Domain>(); }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                Type output = property.ReturnType;
                if (output.Equals(typeof(long))) ld.Add(new Domain.BigInteger());
                else if (output.Equals(typeof(int))) ld.Add(new Domain.Integer());
                else if (output.Equals(typeof(short))) ld.Add(new Domain.SmallInteger());
                else if (output.Equals(typeof(byte))) ld.Add(new Domain.TinyInteger());
                else if (output.Equals(typeof(decimal))) ld.Add(new Domain.Decimal());
                else if (output.Equals(typeof(float))) ld.Add(new Domain.Single());
                else if (output.Equals(typeof(double))) ld.Add(new Domain.Double());
                else if (output.Equals(typeof(bool))) ld.Add(new Domain.Boolean());
                else if (output.Equals(typeof(string))) ld.Add(new Domain.String());
                else if (output.Equals(typeof(DateTime))) ld.Add(new Domain.DateTime());
                else if (output.Equals(typeof(Guid))) ld.Add(new Domain.UniqueIdentifier());
                else throw new Exception("Cannot determine the output type of the property for this function.");
                return ld;
            }
        }

        public object[] apply(params object[] o)
        {
            if (o.Length > 0) throw new FunctionException(String.Concat("Functions generated from properties require 0 inputs, not ", o.Length.ToString(), "."));
            return new object[] { property.Invoke(null, new object[] { }) };
        }

        public XElement ToXElement()
        {
            return null;
        }
        #endregion
    }

    public class CurrentUser : Object, Function
    {
        #region Function Members

        public bool IsInvertible
        {
            get { return false; }
        }

        public Function Inverse
        {
            get { return null; }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get { return new List<GuavaData.Domain.Domain>(); }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                ld.Add(new Domain.String(32));
                return ld;
            }
        }

        public object[] apply(params object[] o)
        {
            if (o.Length > 0) throw new FunctionException(String.Concat("Function CurrentUser requires 0 inputs, not ", o.Length.ToString(), "."));
            return new object[] { Environment.UserName };
        }

        public XElement ToXElement()
        {
            return new XElement("Function", this.GetType().ToString());
        }
        #endregion
    }

    public class CurrentMachine : Object, Function
    {
        #region Function Members

        public bool IsInvertible
        {
            get { return false; }
        }

        public Function Inverse
        {
            get { return null; }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get { return new List<GuavaData.Domain.Domain>(); }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                ld.Add(new Domain.String(32));
                return ld;
            }
        }

        public object[] apply(params object[] o)
        {
            if (o.Length > 0) throw new FunctionException(String.Concat("Function CurrentMachine requires 0 inputs, not ", o.Length.ToString(), "."));
            return new object[] { Environment.MachineName };
        }

        public XElement ToXElement()
        {
            return new XElement("Function", this.GetType().ToString()); 
        }
        #endregion
    }

    public class BitMask : Object, Function, SqlFunctionDefintion
    {
        int numFlags;
        List<Domain.Domain> inputDomains;
        List<Domain.Domain> outputDomains;

        public BitMask(int numFlags)
        {
            this.numFlags = numFlags;
        }

        #region Function Members

        public bool IsInvertible
        {
            get { return true; }
        }

        public Function Inverse
        {
            get { return new ReverseBitMask(numFlags); }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get
            {
                if (inputDomains == null)
                {
                    inputDomains = new List<Domain.Domain>();
                    for (int i = 0; i < numFlags; i++)
                        inputDomains.Add(new Domain.Boolean());
                }
                return inputDomains;
            }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                if (outputDomains == null)
                {
                    outputDomains = new List<Domain.Domain>();
                    outputDomains.Add(new Domain.Integer(0, (2 ^ numFlags) - 1));
                }
                return outputDomains;
            }
        }

        public object[] apply(params object[] o)
        {
            if (o.Length != numFlags) throw new FunctionException("Number of arguments to function BitMask should be " + numFlags.ToString() + ", but " + o.Length.ToString() + " were found.");

            int ret = 0;

            for (int i = 0; i < numFlags; i++)
            {
                bool found = false;
                object temp = o[i].ToString();
                if ((temp.Equals(null)) || (temp.Equals("")))
                {
                    found = true;
                }
                if (!found) //.Equals("false"))
                {
                    if ((bool)(o[i])) ret += (int)Math.Pow(2, i);
                }
            }

            object[] retargs = new object[] { ret };
            return retargs;
        }

        public XElement ToXElement()
        {
            XElement x = new XElement("Function", new XAttribute("type", "GuavaData.Function.BitMask"));
            XElement arg = new XElement("int");
            arg.SetValue(numFlags.ToString());
            x.Add(arg);
            return x;
        }
        #endregion

        #region SqlFunctionDefintion Members

        public string SqlString(int position, params string[] columnInputs)
        {
            // Check the position to make sure that it is a valid position
            if (position != 0)
                throw new Exception("Bitmask: No function defined at output position " + position.ToString() + ".");
            if (columnInputs.Length != numFlags)
                throw new Exception("Bitmask: Number of input places " + numFlags.ToString() + " does not match number of columns entered "
                    + columnInputs.Length.ToString() + ".");

            // Generate the text of the function
            string retVal = "";
            for (int i = 0; i < columnInputs.Length; i++)
                retVal = String.Concat(retVal, "(", columnInputs[i], " * ", ((int)Math.Pow(2, i)).ToString(), ") +");

            return retVal.Substring(0, retVal.Length - 2);
        }

        #endregion
    }
    public class ReverseBitMask : Object, Function, SqlFunctionDefintion
    {
        int numFlags;

        public ReverseBitMask(int numFlags)
        {
            this.numFlags = numFlags;
        }

        #region Function Members

        public bool IsInvertible
        {
            get { return true; }
        }

        public Function Inverse
        {
            get { return new BitMask(numFlags); }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get
            {
                List<GuavaData.Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                ld.Add(new GuavaData.Domain.Integer(0, (2 ^ numFlags) - 1));
                return ld;
            }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<GuavaData.Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                for (int i = 0; i < numFlags; i++) ld.Add(new GuavaData.Domain.Boolean());
                return ld;
            }
        }



        /*      public object[] apply(params object[] o)
              {
                  if (o.Length != 1) throw new FunctionException("Number of arguments to function ReverseBitMask should be " + numFlags.ToString() + ", but " + o.Length.ToString() + " were found.");

                  object[] retargs = new object[numFlags];

                  bool found = false;
                  object temp = o[0].ToString();
                  if ((temp.Equals(null)) || (temp.Equals("")))
                  {
                          found = true;
                  }
                  if (!found) //.Equals("false"))
                  {
                      int oi = (int)o[0];
                      for (int i = 0; i < numFlags; i++)
                      {
                          if ((oi & (2 ^ i)) == 0) retargs[i] = false;
                          else retargs[i] = true;
                      }
                  }

                  return retargs;
              }
      */
        public object[] apply(params object[] o)
        {
            if (o.Length != 1) throw new FunctionException("Number of arguments to function ReverseBitMask should be " + numFlags.ToString() + ", but " + o.Length.ToString() + " were found.");

            object[] retargs = new object[numFlags];

            bool found = false;
            object temp = o[0].ToString();
            if ((temp.Equals(null)) || (temp.Equals("")))
            {
                found = true;
            }
            if (!found) //.Equals("false"))
            {
                int oi = (int)o[0];
                for (int i = 0; i < numFlags; i++)
                {
                    if ((oi & (int)(Math.Pow(2, i))) == 0) retargs[i] = false;
                    else retargs[i] = true;
                }
            }

            return retargs;
        }

        public XElement ToXElement()
        {
            XElement x = new XElement("Function", new XAttribute("type", "GuavaData.Function.ReverseBitMask"));
            XElement arg = new XElement("int");
            arg.SetValue(numFlags.ToString());
            x.Add(arg);
            return x;
        }
        #endregion

        #region SqlFunctionDefintion Members

        public string SqlString(int position, params string[] columnInputs)
        {
            // Check the position to make sure that it is a valid position
            if (position < 0 || position >= numFlags)
                throw new Exception("ReverseBitmask: No function defined at output position " + position.ToString() + ".");
            if (columnInputs.Length != 1)
                throw new Exception("ReverseBitmask: Expected only a single input, but received " + columnInputs.Length.ToString() + ".");

            // Generate the text of the function
            string retVal = "(" + columnInputs[0] + " & " + ((int)Math.Pow(2, position)).ToString() + ")/" + ((int)Math.Pow(2, position)).ToString();
            return retVal;
        }

        #endregion
    }
    public class ColumnConcatenation : Object, Function, SqlFunctionDefintion
    {
        List<string> fs;
        int inputCount;
        List<string> ins;

        public ColumnConcatenation(List<string> formatStrings)
        {
            ins = new List<string>(formatStrings);
            fs = formatStrings;
            inputCount = int.Parse(fs[0]);
            fs.RemoveAt(0);
        }

        #region Function Members

        public bool IsInvertible
        {
            get { return true; }
        }

        public Function Inverse
        {
            get {
                List<string> ls = new List<string>(fs);
                ls.Insert(0, (inputCount + fs.Count).ToString());
                return new ColumnConcatenationInverse(ls);
            }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get
            {
                List<GuavaData.Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                for (int i = 0; i < inputCount; i++)
                {
                    ld.Add(new GuavaData.Domain.String());
                }
                return ld;
            }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<GuavaData.Domain.Domain> ld = inputs;
                for (int i = 0; i < fs.Count; i++) ld.Add(new GuavaData.Domain.String());
                return ld;
            }
        }
        public object[] apply(params object[] o)
        {
            if (o.Length != 1) throw new FunctionException("Column Format needs one and only one column to format, it has been sent " + o.Length.ToString() + ".");

            object[] retargs = new object[fs.Count + 1];

            retargs[0] = o[0];
            string s = (string) o[0];
            //for(int i = 1; i < fs.Count + 1;i++){
            //    object temp=o[0].clone;
            //    retargs[i]=String.Format(fs[i-1],temp);
            //}
            return retargs;
        }

        public XElement ToXElement()
        {
            XElement x = new XElement("Function", new XAttribute("type", "GuavaData.Function.ColumnConcatenation"));
            List<XElement> args = new List<XElement>();
            for (int i = 0; i < ins.Count; i++)
            {
                XElement arg = new XElement("string");
                arg.SetValue(ins[i]);
                args.Add(arg);
            }
            x.Add(args);
            return x;
        }
        #endregion

        #region SqlFunctionDefintion Members

        public string SqlString(int position, params string[] columnInputs)
        {
            string sql = "";
            if (position < columnInputs.Length)
            {
                return columnInputs[position];
            }
            else
            {
                string formatString = fs[position - columnInputs.Length].Clone().ToString();
                string tempString = formatString.Clone().ToString();
                for (int i = 0; i < columnInputs.Count(); i++)
                {
                    int index = tempString.LastIndexOf("%s");
                    if (index >= 0)
                        tempString=tempString.Remove(index, 2);
                    if (i == 0)
                        sql = "STUFF('" + formatString + "', " + (index+1) + ", 2, " + columnInputs[columnInputs.Count() - i - 1] + ")";
                    else
                        sql = "STUFF(" + sql + ", " + (index+1) + ", 2, " + columnInputs[columnInputs.Count() - i - 1] + ")";

                }
            }
            //// Check the position to make sure that it is a valid position
            //if (position < 0 || position >= formatStrings.Count + 1)
            //    throw new Exception("ColumnFormat: No function defined at output position " + position.ToString() + ".");

            //// Generate the text of the function
            //string retVal = "(" + columnInputs[0] + " & " + ((int)Math.Pow(2, position)).ToString() + ")/" + ((int)Math.Pow(2, position)).ToString();
            //return retVal;
            return sql;
        }

        #endregion
    }
    public class ColumnConcatenationInverse : Object, Function, SqlFunctionDefintion
    {
        List<string> ins;
        List<string> fs;
        int inputCount;

        public ColumnConcatenationInverse(List<string> formatStrings)
        {
            ins = new List<string>(formatStrings);
            fs = formatStrings;
            inputCount = int.Parse(fs[0]);
            fs.RemoveAt(0);
        }

        #region Function Members

        public bool IsInvertible
        {
            get { return true; }
        }

        public Function Inverse
        {
            get {
                List<string> ls = new List<string>(fs);
                ls.Insert(0, (inputCount-fs.Count).ToString());
                return new ColumnConcatenation(ls); 
            }
        }

        public List<GuavaData.Domain.Domain> inputs
        {
            get
            {
                List<GuavaData.Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                for (int i = 0; i < inputCount; i++)
                {
                    ld.Add(new GuavaData.Domain.String());
                }
                return ld;
            }
        }

        public List<GuavaData.Domain.Domain> outputs
        {
            get
            {
                List<GuavaData.Domain.Domain> ld = new List<GuavaData.Domain.Domain>();
                for (int i = 0; i < inputCount-fs.Count; i++) ld.Add(new GuavaData.Domain.String());
                return ld;
            }
        }
        public object[] apply(params object[] o)
        {
            if (o.Length != 1) throw new FunctionException("Column Format needs one and only one column to format, it has been sent " + o.Length.ToString() + ".");

            object[] retargs = new object[fs.Count + 1];

            retargs[0] = o[0];
            //string s = (string) o[0];
            //for(int i = 1; i < columns.Count + 1;i++){
            //    object temp=o[0].clone;
            //    retargs[i]=String.Format(columns[i-1],temp);
            //}
            return retargs;
        }

        public XElement ToXElement()
        {
            XElement x = new XElement("Function", new XAttribute("type", "GuavaData.Function.ColumnConcatenationInverse"));
            List<XElement> args = new List<XElement>();
            for (int i = 0; i < ins.Count; i++)
            {
                XElement arg = new XElement("string");
                arg.SetValue(ins[i]);
                args.Add(arg);
            }
            x.Add(args);
            return x;
        }
        #endregion

        #region SqlFunctionDefintion Members

        public string SqlString(int position, params string[] columnInputs)
        {
            return columnInputs[position];
        }

        #endregion

    }

}
